/**
 * 逆变：
 * 当 ‘函数作为参数’ 时，情况就变了
*/

class Animal {
    doAnimalThing(): void {
        console.log("I am a Animal!")
    }
}
class Dog extends Animal {
    doDogThing(): void {
        console.log("I am a Dog!")
    }
}
class Cat extends Animal {
    doCatThing(): void {
        console.log("I am a Cat!")
    }
}
/**
 * 不支持协变的情况，那就是 ‘函数作为参数’ 时
 * 
 * 因为【调用方是 doAnimalThingAction(new Cat())】，这里可以传Animal的子类(协变)，cat。
 * 而【定义方 doDogThing函数的参数 却是Dog类型】，如果能成功，那么 cat 就做了 doDogThing 了，显然类型就不安全了 。
*/
function MakeItDoSomething(doAnimalThingAction: (ani: Animal) => void) {
    //调用方
    doAnimalThingAction(new Cat())
}
//定义方
function doDogThing(dog: Dog){
    dog.doDogThing()
}
//以下代码会报错
MakeItDoSomething(doDogThing)
//下面的代码同理：
function MakeItDoSomethingA(ani: Animal){}
const fn: typeof MakeItDoSomethingA = doDogThing

/**
 * 逆变：
 * 为了保证【调用方】传入的参数类型拥有的属性和方法 一定 大于等于 【定义方】定义参数类型拥有的属性和方法。
 * 所以这里【doDogThing函数的参数的类型】，也就是dog的类型，一定要是Animal 类型或者Animal的父类，要确保拥有的属性和方法尽可能的少。
 * 父类(Object)赋值给子类(Animal)的情况，就是逆变。
*/

// 定义方
function doDogThing1(dog: Object){
    dog.toString()
}
function doDogThing2(dog: Animal){
    dog.doAnimalThing()
}
// 不报错了。
MakeItDoSomething(doDogThing1)
MakeItDoSomething(doDogThing2) 
// 不报错了。
function doAnimalThingAction(ani: Animal){}
type AniFuncType = typeof doAnimalThingAction
const fn1: AniFuncType = doDogThing1
const fn2: AniFuncType = doDogThing2

/**
 * 最后来总结一下：
 * 不管是协变还是逆变，都是为了达到一个目的，
 * 那就是【增加调用方参数类型拥有的属性和方法数量】，或者【减少定义方参数拥有的属性和数量】，来保证类型安全。
 * 函数类型的赋值，要考虑逆变的情况。
*/

//export {}：解决“无法重新声明块范围变量”错误提示问题
export { }

